﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using Informant.Contacts;
using Informant.SmsMessaging;
using Informant.UI.SmsService;

namespace Informant.UI.ViewModels
{
	public class SmsMessagingViewModel : BindableObject
	{
		#region Fields

		private readonly List<Contact> _selectedContacts;
		private readonly List<Group> _selectedGroups;
		private ObservableCollection<SelectableItem<Contact>> _contacts;
		private ObservableCollection<SelectableItem<Group>> _groups;
		private String _messageBody;
		private Boolean _targetingContacts;
		private List<Phone> _recipients;
		private ObservableCollection<Phone> _rejectedRecipients;
		private Boolean _isLoadingContacts;
		private Boolean _isLoadingGroups;
		private GroupingMethod _groupingMethod;
		private Boolean _isSendingSms;
		private Int32 _sentSms;
		private Int32 _failedSms;
		private Int32 _totalSms;

		#endregion

		#region Properties

		/// <summary>
		/// Gets or sets the contacts.
		/// </summary>
		/// <value>The contacts.</value>
		public ObservableCollection<SelectableItem<Contact>> Contacts
		{
			get { return _contacts; }
			set
			{
				_contacts = value;
				PropertyChange("Contacts");
			}
		}
		/// <summary>
		/// Gets or sets the groups.
		/// </summary>
		/// <value>The groups.</value>
		public ObservableCollection<SelectableItem<Group>> Groups
		{
			get { return _groups; }
			set
			{
				_groups = value;
				PropertyChange("Groups");
			}
		}
		/// <summary>
		/// Gets or sets the max message length.
		/// </summary>
		/// <value>The max message length.</value>
		public Int32 MaxMessageLength
		{
			get
			{
				Int32 maxMessageLength;
				Int32.TryParse(SmsResources.MaxMessageLength, out maxMessageLength);
				return maxMessageLength;
			}
		}
		/// <summary>
		/// Gets or sets the message body.
		/// </summary>
		/// <value>The message body.</value>
		public String MessageBody
		{
			get { return _messageBody; }
			set
			{
				_messageBody = value;
				PropertyChange("MessageBody");
			}
		}
		/// <summary>
		/// Gets or sets a value indicating whether [targeting contacts].
		/// </summary>
		/// <value><c>true</c> if [targeting contacts]; otherwise [targeting groups], <c>false</c>.</value>
		public Boolean TargetingContacts
		{
			get { return _targetingContacts; }
			set
			{
				_targetingContacts = value;
				PropertyChange("TargetingContacts");
			}
		}
		/// <summary>
		/// Gets the recipients to receive the message when the SendSms
		/// command is called.
		/// </summary>
		/// <value>The recipients.</value>
		public List<Phone> Recipients
		{
			[DebuggerStepThrough]
			get { return _recipients; }
			private set
			{
				if (value == _recipients)
					return;

				_recipients = value;
				PropertyChange("Recipients");
			}
		}
		/// <summary>
		/// Gets the rejected recipients, if any, 
		///	from the previous time the SendSms command was called.
		/// </summary>
		/// <value>The rejected recipients.</value>
		public ObservableCollection<Phone> RejectedRecipients
		{
			get { return _rejectedRecipients; }
			private set
			{
				_rejectedRecipients = value;
				PropertyChange("RejectedRecipients");
			}
		}
		/// <summary>
		/// Gets or sets a value indicating whether this instance is loading contacts.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is loading contacts; otherwise, <c>false</c>.
		/// </value>
		public Boolean IsLoadingContacts
		{
			get { return _isLoadingContacts; }
			set
			{
				_isLoadingContacts = value;
				PropertyChange("IsLoadingContacts");
			}
		}
		/// <summary>
		/// Gets or sets a value indicating whether this instance is loading groups.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is loading groups; otherwise, <c>false</c>.
		/// </value>
		public Boolean IsLoadingGroups
		{
			get { return _isLoadingGroups; }
			set
			{
				_isLoadingGroups = value;
				PropertyChange("IsLoadingGroups");
			}
		}
		/// <summary>
		/// Gets or sets the grouping method.
		/// </summary>
		/// <value>The grouping method.</value>
		public GroupingMethod GroupingMethod
		{
			get { return _groupingMethod; }
			set
			{
				_groupingMethod = value;
				PropertyChange("GroupingMethod");
				UpdateRecipients();
			}
		}
		/// <summary>
		/// Gets or sets a value indicating whether this instance is sending SMS.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is sending SMS; otherwise, <c>false</c>.
		/// </value>
		public Boolean IsSendingSms
		{
			get { return _isSendingSms; }
			set
			{
				_isSendingSms = value;
				PropertyChange("IsSendingSms");
			}
		}
		/// <summary>
		/// Gets or sets the progress of successful sent SMS.
		/// </summary>
		/// <value>The sent SMS.</value>
		public Int32 SentSms
		{
			get { return _sentSms; }
			private set
			{
				if (_sentSms != value)
				{
					_sentSms = value;
					PropertyChange("SentSms");
				}
			}
		}
		/// <summary>
		/// Gets or sets the progress of failed SMS.
		/// </summary>
		/// <value>The failed SMS.</value>
		public Int32 FailedSms
		{
			get { return _failedSms; }
			private set
			{
				if (_failedSms != value)
				{
					_failedSms = value;
					PropertyChange("FailedSms");
				}
			}
		}
		/// <summary>
		/// Gets or sets the total SMS to be sent.
		/// </summary>
		/// <value>The total SMS.</value>
		public Int32 TotalSms
		{
			get { return _totalSms; }
			private set
			{
				if (_totalSms != value)
				{
					_totalSms = value;
					PropertyChange("TotalSms");
				}
			}
		}

		#endregion

		#region Construction

		public SmsMessagingViewModel()
		{
			_selectedContacts = new List<Contact>();
			_selectedGroups = new List<Group>();
			MessageBody = String.Empty;

			SendSms = new DelegateCommand<Object>(SendSmsHandler, CanSendSms, new String[] { "Recipients", "MessageBody" });
			ChangeItemIsSelected = new DelegateCommand<Object>(ChangeItemIsSelectedHandler);

			RetrieveContacts();
			RetrieveGroups();
		}

		#endregion

		#region Commands

		/// <summary>
		/// Sends an sms.
		/// </summary>
		/// <value>The send SMS.</value>
		public DelegateCommand<Object> SendSms { get; set; }
		private Boolean CanSendSms(Object obj)
		{
			return !String.IsNullOrEmpty(MessageBody)
				&& MessageBody.Length <= MaxMessageLength
				&& (Recipients != null && Recipients.Count() > 0);
		}
		private void SendSmsHandler(Object obj)
		{
			IsSendingSms = true;
			RejectedRecipients = null;
			Sms sms = new Sms
						{
							Recipients = Recipients,
							Messages = new Message[] { new Message { Text = MessageBody } }.ToList()
						};
			ApplicationContext.Instance.SendSms(sms, SendSmsCallback, SendSmsProgressCallback);
			MessageBody = String.Empty;
		}
		private void SendSmsCallback(Boolean succeded)
		{
			if (succeded)
			{
				RejectedRecipients = null;
			}
			IsSendingSms = false;
		}
		private void SendSmsProgressCallback(Int32 sent, Int32 failed, Int32 total)
		{
			Debug.WriteLine("---");
			Debug.WriteLine("Sent: " + sent);
			Debug.WriteLine("Failed: " + failed);
			Debug.WriteLine("Total: " + total);
			Debug.WriteLine("---");
			SentSms = sent;
			FailedSms = failed;
			TotalSms = total;
		}

		/// <summary>
		/// Adds or removes items from the collection of selected items they belong to.
		/// </summary>
		/// <value>The change item is selected.</value>
		public DelegateCommand<Object> ChangeItemIsSelected { get; set; }
		private void ChangeItemIsSelectedHandler(Object obj)
		{
			SelectableItem<Contact> selectableContact = obj as SelectableItem<Contact>;
			SelectableItem<Group> selectableGroup;
			if (selectableContact != null)
			{
				if (selectableContact.IsSelected)
				{
					_selectedContacts.Add(selectableContact.Target);
				}
				else if (_selectedContacts.Contains(selectableContact.Target))
				{
					_selectedContacts.Remove(selectableContact.Target);
				}
			}
			else if ((selectableGroup = obj as SelectableItem<Group>) != null)
			{
				if (selectableGroup.IsSelected)
				{
					_selectedGroups.Add(selectableGroup.Target);
				}
				else if (_selectedGroups.Contains(selectableGroup.Target))
				{
					_selectedGroups.Remove(selectableGroup.Target);
				}
			}
			UpdateRecipients();
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Updates the recipients.
		/// </summary>
		private void UpdateRecipients()
		{
			if (TargetingContacts)
			{
				Recipients = _selectedContacts.Select(contact => contact.Phones.FirstOrDefault()).ToList();
			}
			else
			{
				IEnumerable<SelectableItem<Contact>> contactsToNotify = null;
				switch (GroupingMethod)
				{
					case GroupingMethod.Union:
						contactsToNotify = Contacts.Where(contact => contact.Target.Groups.Any(cGroup => _selectedGroups.Any(sGroup => sGroup.Id == cGroup.Id)));
						break;
					case GroupingMethod.Intersect:
						List<IEnumerable<SelectableItem<Contact>>> contactGroupings = new List<IEnumerable<SelectableItem<Contact>>>();
						foreach (Group group in _selectedGroups)
						{
							Group tempGroup = group;
							contactGroupings.Add(Contacts.Where(contact => contact.Target.Groups.Any(cGroup => tempGroup.Id == cGroup.Id)));
						}
						if (contactGroupings.Count > 0)
						{
							contactsToNotify = contactGroupings[0];
							for (int i = 1; i < contactGroupings.Count; i++)
							{
								contactsToNotify = contactsToNotify.Intersect(contactGroupings[i]);
							}
						}
						break;
				}
				Recipients = contactsToNotify != null ? contactsToNotify.Select(contact => contact.Target.Phones.FirstOrDefault()).ToList() : null;
			}
		}
		/// <summary>
		/// Retrieves the contacts.
		/// </summary>
		private void RetrieveContacts()
		{
			IsLoadingContacts = true;
			ApplicationContext.Instance.RetrieveContacts(RetrieveContactsCallback);
		}
		/// <summary>
		/// Callback for RetrieveContacts.
		/// </summary>
		/// <param name="contacts">The contacts.</param>
		private void RetrieveContactsCallback(IEnumerable<Contact> contacts)
		{
			Contacts = new ObservableCollection<SelectableItem<Contact>>(
				contacts
					.Where(contact => contact.Phones != null)
					.Where(contact => contact.Phones.Any(phone => phone.Kind == InformationKind.Mobile))
					.OrderBy(contact => contact.FirstName)
					.ThenBy(contact => contact.LastName)
					.Select(contact =>
							new SelectableItem<Contact>(contact)));
			IsLoadingContacts = false;
		}
		/// <summary>
		/// Retrieves the groups.
		/// </summary>
		private void RetrieveGroups()
		{
			IsLoadingGroups = true;
			ApplicationContext.Instance.RetrieveGroups(RetrieveGroupsCallback);
		}
		/// <summary>
		/// Callback for RetrieveGroups.
		/// </summary>
		/// <param name="groups">The groups.</param>
		private void RetrieveGroupsCallback(IEnumerable<Group> groups)
		{
			Groups = new ObservableCollection<SelectableItem<Group>>(
				groups
					.OrderBy(group=> group.Name)
					.Select(group => new SelectableItem<Group>(group)));
			IsLoadingGroups = false;
		}

		#endregion
	}
}
